#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sstream>
#include <fstream>
#include <time.h>
#include <iostream>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <utility>
#include <signal.h>
#include "webserver.h"
#define SERVERPORT 3400
#define BACKLOG 10 	/* how many pending connections queue will hold */
#define BUFFERSIZE 1024

using namespace std;

///////////////////////////////////////////////////////////////////////////////
//
// Global Variables
//
///////////////////////////////////////////////////////////////////////////////

string serverDirectory;
tdExtensionMap extensionMap = create_extension_map();
tdMethodMap methodMap = create_method_map();
tdFieldMap fieldMap = create_field_map();
tdStatusMap statusMap = create_status_map();

///////////////////////////////////////////////////////////////////////////////
//
// get_date_header() returns the proper formatted date header to be used in
// the response
//
///////////////////////////////////////////////////////////////////////////////

string get_date_header()
{
	time_t t;
	time(&t);
  
	stringstream ss;
	ss << "Date: " << asctime(gmtime(&t));
	return ss.str();
}

///////////////////////////////////////////////////////////////////////////////
//
// get_status_message (int code) returns the proper formatted status header 
// to be used in the response based off of the code parameter
//
///////////////////////////////////////////////////////////////////////////////

string get_status_line (int code)
{
	tdStatusMap::iterator it = statusMap.find(code);
	
	if (it != statusMap.end())
	{
		return it->second;
	}
	else
	{
		return "";
	}
}

///////////////////////////////////////////////////////////////////////////////
//
// get_content_type (string file) returns the proper formatted content type
// header to be used in the response based off of the requested file
//
///////////////////////////////////////////////////////////////////////////////

string get_content_type (string& file)
{
	string ret;

	size_t loc = file.find_last_of('.');
	string extension = file.substr(loc+1);
	
	tdExtensionMap::iterator it = extensionMap.find(extension);
	
	if (it != extensionMap.end())
	{
		switch (it->second)
		{
		case GIF:
			ret = "image/gif";
			break;
		case HTML:
			ret = "text/html";
			break;
		case JPEG:
			ret = "image/jpg";
			break;
		case PNG:
			ret = "image/png";
			break;
		case TIFF:
			ret = "image/tiff";
			break;
		case TXT:
			ret = "text/plain";
			break;
		default:
			break;
		}
	}
	
	return ret;
}

///////////////////////////////////////////////////////////////////////////////
//
// get_field_data returns the data stored in fieldDataMap with key field.  If
// field is not in fieldDataMap, the empty string is returned
//
///////////////////////////////////////////////////////////////////////////////

string get_field_data ( tdFieldDataMap& fieldDataMap, HTTP_HEADER_FIELD field)
{
	tdFieldDataMap::iterator it = fieldDataMap.find(field);
	
	if (it != fieldDataMap.end())
	{
		return it->second;
	}
	else
	{
		return "";
	}
}

///////////////////////////////////////////////////////////////////////////////
//
// returns response based off of method
//
///////////////////////////////////////////////////////////////////////////////

string get_response_for_method ( string& method, string& request_uri, tdFieldDataMap& fieldDataMap )
{
	tdMethodMap::iterator it = methodMap.find(method);
	string filepath;
	
	char buffer[BUFFERSIZE];
	stringstream response;
	ifstream filestream;
	long length;
    
	if (it != methodMap.end())
	{
		switch (it->second)
		{
		case GET:
			filepath = serverDirectory + request_uri;
			
			filestream.open(filepath.c_str());
			if (filestream.is_open())
			{
				// successfully opened file
				response << get_status_line(200) << endl;
				response << get_date_header();
				
				// get content-type
				response << "Content-Type: " << get_content_type(filepath) << endl;
				filestream.seekg(0, ios::end);
				length = filestream.tellg();
				response << "Content-Length: " << length << endl;
				response << endl;
				
				filestream.seekg (0, ios::beg);				
				while( filestream.good() )
				{
					filestream.read(buffer,BUFFERSIZE);
					
					response.write(buffer, filestream.gcount());
				}
				
				filestream.close();
			}
			else
			{
				// unable to open file
				response << get_status_line(404) << endl;
				response << get_date_header();
				response << "Content-Type: text/plain" << endl;
				response << endl;
				response << "Server unable to find requested file" << endl;
			}
			
			break;
		case POST:
			break;
		case HEAD:
			break;
		case DELETE:
			break;
		default:
			break;
		}
	}
	
	return response.str();
}

void add_field_data( tdFieldDataMap& fieldDataMap, string& line )
{
	string field, data;
	size_t found = line.find_first_of(':');

	// extract field name and data from line
	if ( found != string::npos )
	{
		field = line.substr(0, found);
		data = line.substr(found+1);
	}
	else
	{
		field = "";
		data = line;
	}

	// add field data to map
	tdFieldMap::iterator it = fieldMap.find(field);
    
	if (it != fieldMap.end())
	{
		fieldDataMap.insert( pair<HTTP_HEADER_FIELD, string>(it->second, data) );
	}
}

void get_method_info( string& line, string& method, string& request_uri )
{
	size_t loc1 = line.find_first_of(' ');
	method = line.substr(0,loc1);
	
	loc1++;
	size_t loc2 = line.find_first_of(' ', loc1);
	request_uri = line.substr(loc1,loc2-loc1);
}

string get_response( string request )
{
	size_t curr = 0, last = 0;
	string line, method, request_uri;
	
	tdFieldDataMap requestFieldData;
	
	// specical case for first line since it
	// does not have an http header field
	curr = request.find_first_of('\n');
	if ( curr != string::npos )
	{
		// get line
		line = request.substr(last, (curr - last));
		
		// get method name and request uri
		get_method_info(line, method, request_uri);
		
		// find next line
		last = curr + 1;
		curr = request.find_first_of('\n', last);
	}
	
	// for each line with http header field
	while ( curr != string::npos )
	{
		// get line
		line = request.substr(last, (curr - last));
		
		// add field data to map
		add_field_data(requestFieldData, line);
		
		// find next line
		last = curr + 1;
		curr = request.find_first_of('\n', last);
	}
	
	// get response for method
	return get_response_for_method(method, request_uri, requestFieldData);
}

void sigchld_handler(int s)
{
    while(waitpid(-1, NULL, WNOHANG) > 0);
}

void processClient(int sock, in_addr client_addr)
{
   char buffer[BUFFERSIZE];
   bzero(buffer,BUFFERSIZE);
	stringstream request;
	int n;

	do
	{
		// read request
		n = read(sock,buffer,BUFFERSIZE);
		
		if (n < 0)
		{
			perror("error: failed to read message\n");
			exit(1);
		}
	
		request << buffer;
		bzero(buffer,BUFFERSIZE);
	}
	while (n >= BUFFERSIZE);
	
	// print request to console
	cout << "server: got request from " << inet_ntoa(client_addr) << endl;
	cout << request.str() << endl;
	
	string response = get_response(request.str());
	
	n = write(sock,response.c_str(),response.size());
	if (n < 0)
	{
		perror("error: failed to write message");
		exit(1);
	}
}

int main(int argc, char **argv)
{
	int sockfd, newsockfd;	/* listen on sock_fd, new connection on new_fd */
	struct sockaddr_in server_addr; 	/* my address */
	struct sockaddr_in client_addr; /* connector addr */
	struct sigaction sa; /* for signal SIGCHLD */
	socklen_t sin_size;
	int pid;
	
	// get directory of server
	serverDirectory = argv[0];
	serverDirectory = serverDirectory.substr(0,serverDirectory.find_last_of("\\/"));

	// create socket
	if ((sockfd = socket(PF_INET, SOCK_STREAM, 0))==-1)
	{
		perror("error: failed to create socket\n");
		exit(1);
	}
	
	// setup my socket info
	server_addr.sin_family = AF_INET; 	/* host byte order */
	server_addr.sin_port = htons(SERVERPORT); /* short, network byte order */
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	/* INADDR_ANY allows clients to connect to any one of the host’s IP address */
	bzero(&(server_addr.sin_zero), 8); /* zero the struct */

	// bind socket
	if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) 
	{
		perror("error: failed to bind socket\n");
		exit(1);
	}
	
	// listen to socket
	if (listen(sockfd, BACKLOG) == -1) 
	{
		perror("error: failed to listen to socket\n");
		exit(1);
	}
	
	sin_size = sizeof(struct sockaddr_in);
	
	/****** Kill Zombie Processes ******/
	sa.sa_handler = sigchld_handler; // reap all dead processes
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = SA_RESTART;
	if (sigaction(SIGCHLD, &sa, NULL) == -1) 
	{
		perror("sigaction");
		exit(1);
	}
	/*********************************/
	
	// accept clients
	while(1)
	{ 
		if ((newsockfd = accept(sockfd, (struct sockaddr*) &client_addr,&sin_size)) == -1) 
		{
			perror("error: failed to accept client\n");
			continue;
		}
		
		cout << "server: got connection from " << inet_ntoa(client_addr.sin_addr) << endl;
		
		pid = fork();
		if (pid < 0)
		{
			perror("error: failed to fork new process");
		}
		
		if (pid == 0)
		{
			close(sockfd);
			processClient(newsockfd, client_addr.sin_addr);
			exit(0);
		}
		else
		{
			close(newsockfd);
		}
	}
	
	exit(0);
}
		
		
